{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ScitaPqhKtuW"
      },
      "source": [
        "##### Copyright 2018 The TensorFlow Hub Authors.\n",
        "\n",
        "Licensed under the Apache License, Version 2.0 (the \"License\");"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "bNnChGfZK2_w"
      },
      "outputs": [],
      "source": [
        "# Copyright 2018 The TensorFlow Hub Authors. All Rights Reserved.\n",
        "#\n",
        "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "#     http://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License.\n",
        "# =============================================================================="
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9Z_ZvMk5JPFV"
      },
      "source": [
        "# 転移学習で花を分類する\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MfBg1C5NB3X0"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>     <a target=\"_blank\" href=\"https://www.tensorflow.org/hub/tutorials/image_feature_vector\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org で表示</a>   </td>\n",
        "  <td>     <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ja/hub/tutorials/image_feature_vector.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab で実行</a>   </td>\n",
        "  <td>     <a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ja/hub/tutorials/image_feature_vector.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">\tGitHub でソースを表示</a>\n",
        "</td>\n",
        "  <td>     <a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ja/hub/tutorials/image_feature_vector.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">ノートブックをダウンロード</a>   </td>\n",
        "  <td>     <a href=\"https://tfhub.dev/google/imagenet/mobilenet_v2_035_128/feature_vector/2\"><img src=\"https://www.tensorflow.org/images/hub_logo_32px.png\">TF Hub モデルを参照</a>   </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gh-LWtlqLtgH"
      },
      "source": [
        "きれいな花を見つけて、何の花かと思ったことはありませんか？そう思ったのはあなただけではありません。写真から花の種類を特定する方法を構築しましょう！\n",
        "\n",
        "画像の分類については、*畳み込みニューラルネットワーク*という特定の種類の*ディープニューラルネットワーク*が特に強力であることが実証されました。ただし、最新の畳み込みニューラルネットワークには数百ものパラメータがあります。ゼロからトレーニングするには大量のラベル付きのトレーニングデータと多大な計算能力（数百時間以上の GPU）が必要ですが、ここでは 3000 枚のラベル付き写真しかなく、あまり時間をかけられないため、多少頭を使う必要があります。\n",
        "\n",
        "ここではトレーニング済みのネットワーク（約 100 万個の一般的な画像でトレーニング）を用いた*転移学習*というテクニックを使用して、特徴量を抽出し、花の画像を分類するというタスク向けにさらに新しいレイヤーをトレーニングします。\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ORy-KvWXGXBo"
      },
      "source": [
        "## セットアップ\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NTrs9zBKJK1c"
      },
      "outputs": [],
      "source": [
        "import collections\n",
        "import io\n",
        "import math\n",
        "import os\n",
        "import random\n",
        "from six.moves import urllib\n",
        "\n",
        "from IPython.display import clear_output, Image, display, HTML\n",
        "\n",
        "import tensorflow.compat.v1 as tf\n",
        "tf.disable_v2_behavior()\n",
        "\n",
        "import tensorflow_hub as hub\n",
        "\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "import seaborn as sns\n",
        "import sklearn.metrics as sk_metrics\n",
        "import time"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Do-T63G7NCSB"
      },
      "source": [
        "## Flowers データセット\n",
        "\n",
        "flowers データセットは、可能性のある 5 つのクラスラベルを伴う花の画像で構成されています。\n",
        "\n",
        "機械学習モデルをトレーニングする際、データを training と test データセットに分割します。training データを使ってモデルをトレーニングしてから、モデルが分析したことのないデータ（test セット）でのモデルのパフォーマンスを評価します。\n",
        "\n",
        "training と test サンプルをダウンロードし（時間がかかる場合があります）、training と test のセットに分割しましょう。\n",
        "\n",
        "次の 2 つのセルを実行します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "both",
        "id": "HYQr1SILIxSK"
      },
      "outputs": [],
      "source": [
        "FLOWERS_DIR = './flower_photos'\n",
        "TRAIN_FRACTION = 0.8\n",
        "RANDOM_SEED = 2018\n",
        "\n",
        "\n",
        "def download_images():\n",
        "  \"\"\"If the images aren't already downloaded, save them to FLOWERS_DIR.\"\"\"\n",
        "  if not os.path.exists(FLOWERS_DIR):\n",
        "    DOWNLOAD_URL = 'http://download.tensorflow.org/example_images/flower_photos.tgz'\n",
        "    print('Downloading flower images from %s...' % DOWNLOAD_URL)\n",
        "    urllib.request.urlretrieve(DOWNLOAD_URL, 'flower_photos.tgz')\n",
        "    !tar xfz flower_photos.tgz\n",
        "  print('Flower photos are located in %s' % FLOWERS_DIR)\n",
        "\n",
        "\n",
        "def make_train_and_test_sets():\n",
        "  \"\"\"Split the data into train and test sets and get the label classes.\"\"\"\n",
        "  train_examples, test_examples = [], []\n",
        "  shuffler = random.Random(RANDOM_SEED)\n",
        "  is_root = True\n",
        "  for (dirname, subdirs, filenames) in tf.gfile.Walk(FLOWERS_DIR):\n",
        "    # The root directory gives us the classes\n",
        "    if is_root:\n",
        "      subdirs = sorted(subdirs)\n",
        "      classes = collections.OrderedDict(enumerate(subdirs))\n",
        "      label_to_class = dict([(x, i) for i, x in enumerate(subdirs)])\n",
        "      is_root = False\n",
        "    # The sub directories give us the image files for training.\n",
        "    else:\n",
        "      filenames.sort()\n",
        "      shuffler.shuffle(filenames)\n",
        "      full_filenames = [os.path.join(dirname, f) for f in filenames]\n",
        "      label = dirname.split('/')[-1]\n",
        "      label_class = label_to_class[label]\n",
        "      # An example is the image file and it's label class.\n",
        "      examples = list(zip(full_filenames, [label_class] * len(filenames)))\n",
        "      num_train = int(len(filenames) * TRAIN_FRACTION)\n",
        "      train_examples.extend(examples[:num_train])\n",
        "      test_examples.extend(examples[num_train:])\n",
        "\n",
        "  shuffler.shuffle(train_examples)\n",
        "  shuffler.shuffle(test_examples)\n",
        "  return train_examples, test_examples, classes\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "_9NklpcANhtB"
      },
      "outputs": [],
      "source": [
        "# Download the images and split the images into train and test sets.\n",
        "download_images()\n",
        "TRAIN_EXAMPLES, TEST_EXAMPLES, CLASSES = make_train_and_test_sets()\n",
        "NUM_CLASSES = len(CLASSES)\n",
        "\n",
        "print('\\nThe dataset has %d label classes: %s' % (NUM_CLASSES, CLASSES.values()))\n",
        "print('There are %d training images' % len(TRAIN_EXAMPLES))\n",
        "print('there are %d test images' % len(TEST_EXAMPLES))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tHF7bHTfnD6S"
      },
      "source": [
        "## データを確認する\n",
        "\n",
        "flowers データセットは、ラベル付きの花の画像のサンプルで構成されています。各サンプルには、JPEG 形式の花の画像と、花の種類を示すクラスラベルが含まれます。いくつかの画像をラベルとともに表示してみましょう。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "both",
        "id": "1friUvN6kPYM"
      },
      "outputs": [],
      "source": [
        "#@title Show some labeled images\n",
        "def get_label(example):\n",
        "  \"\"\"Get the label (number) for given example.\"\"\"\n",
        "  return example[1]\n",
        "\n",
        "def get_class(example):\n",
        "  \"\"\"Get the class (string) of given example.\"\"\"\n",
        "  return CLASSES[get_label(example)]\n",
        "\n",
        "def get_encoded_image(example):\n",
        "  \"\"\"Get the image data (encoded jpg) of given example.\"\"\"\n",
        "  image_path = example[0]\n",
        "  return tf.gfile.GFile(image_path, 'rb').read()\n",
        "\n",
        "def get_image(example):\n",
        "  \"\"\"Get image as np.array of pixels for given example.\"\"\"\n",
        "  return plt.imread(io.BytesIO(get_encoded_image(example)), format='jpg')\n",
        "\n",
        "def display_images(images_and_classes, cols=5):\n",
        "  \"\"\"Display given images and their labels in a grid.\"\"\"\n",
        "  rows = int(math.ceil(len(images_and_classes) / cols))\n",
        "  fig = plt.figure()\n",
        "  fig.set_size_inches(cols * 3, rows * 3)\n",
        "  for i, (image, flower_class) in enumerate(images_and_classes):\n",
        "    plt.subplot(rows, cols, i + 1)\n",
        "    plt.axis('off')\n",
        "    plt.imshow(image)\n",
        "    plt.title(flower_class)\n",
        "\n",
        "NUM_IMAGES = 15 #@param {type: 'integer'}\n",
        "display_images([(get_image(example), get_class(example))\n",
        "               for example in TRAIN_EXAMPLES[:NUM_IMAGES]])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Hyjr6PuboTAg"
      },
      "source": [
        "## モデルを構築する\n",
        "\n",
        "[TF-Hub](https://tensorflow.org/hub) の画像特徴量ベクトルモジュールを読み込み、それに線形分類器をスタックし、トレーニングと検証の演算を追加することにします。次のセルは、モデルとそのトレーニングを説明する TF グラフを構築しますが、トレーニングを実行しません（この後のステップで行います）。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "LbkSRaK_oW5Y"
      },
      "outputs": [],
      "source": [
        "LEARNING_RATE = 0.01\n",
        "\n",
        "tf.reset_default_graph()\n",
        "\n",
        "# Load a pre-trained TF-Hub module for extracting features from images. We've\n",
        "# chosen this particular module for speed, but many other choices are available.\n",
        "image_module = hub.Module('https://tfhub.dev/google/imagenet/mobilenet_v2_035_128/feature_vector/2')\n",
        "\n",
        "# Preprocessing images into tensors with size expected by the image module.\n",
        "encoded_images = tf.placeholder(tf.string, shape=[None])\n",
        "image_size = hub.get_expected_image_size(image_module)\n",
        "\n",
        "\n",
        "def decode_and_resize_image(encoded):\n",
        "  decoded = tf.image.decode_jpeg(encoded, channels=3)\n",
        "  decoded = tf.image.convert_image_dtype(decoded, tf.float32)\n",
        "  return tf.image.resize_images(decoded, image_size)\n",
        "\n",
        "\n",
        "batch_images = tf.map_fn(decode_and_resize_image, encoded_images, dtype=tf.float32)\n",
        "\n",
        "# The image module can be applied as a function to extract feature vectors for a\n",
        "# batch of images.\n",
        "features = image_module(batch_images)\n",
        "\n",
        "\n",
        "def create_model(features):\n",
        "  \"\"\"Build a model for classification from extracted features.\"\"\"\n",
        "  # Currently, the model is just a single linear layer. You can try to add\n",
        "  # another layer, but be careful... two linear layers (when activation=None)\n",
        "  # are equivalent to a single linear layer. You can create a nonlinear layer\n",
        "  # like this:\n",
        "  # layer = tf.layers.dense(inputs=..., units=..., activation=tf.nn.relu)\n",
        "  layer = tf.layers.dense(inputs=features, units=NUM_CLASSES, activation=None)\n",
        "  return layer\n",
        "\n",
        "\n",
        "# For each class (kind of flower), the model outputs some real number as a score\n",
        "# how much the input resembles this class. This vector of numbers is often\n",
        "# called the \"logits\".\n",
        "logits = create_model(features)\n",
        "labels = tf.placeholder(tf.float32, [None, NUM_CLASSES])\n",
        "\n",
        "# Mathematically, a good way to measure how much the predicted probabilities\n",
        "# diverge from the truth is the \"cross-entropy\" between the two probability\n",
        "# distributions. For numerical stability, this is best done directly from the\n",
        "# logits, not the probabilities extracted from them.\n",
        "cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(logits=logits, labels=labels)\n",
        "cross_entropy_mean = tf.reduce_mean(cross_entropy)\n",
        "\n",
        "# Let's add an optimizer so we can train the network.\n",
        "optimizer = tf.train.GradientDescentOptimizer(learning_rate=LEARNING_RATE)\n",
        "train_op = optimizer.minimize(loss=cross_entropy_mean)\n",
        "\n",
        "# The \"softmax\" function transforms the logits vector into a vector of\n",
        "# probabilities: non-negative numbers that sum up to one, and the i-th number\n",
        "# says how likely the input comes from class i.\n",
        "probabilities = tf.nn.softmax(logits)\n",
        "\n",
        "# We choose the highest one as the predicted class.\n",
        "prediction = tf.argmax(probabilities, 1)\n",
        "correct_prediction = tf.equal(prediction, tf.argmax(labels, 1))\n",
        "\n",
        "# The accuracy will allow us to eval on our test set. \n",
        "accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0vvhYQ7-3AG_"
      },
      "source": [
        "## ネットワークをトレーニングする\n",
        "\n",
        "ラベルの構築が完了したので、test セットを使ってトレーニングし、そのパフォーマンスを確認することにしましょう。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1YnBg7-OS3Dz"
      },
      "outputs": [],
      "source": [
        "# How long will we train the network (number of batches).\n",
        "NUM_TRAIN_STEPS = 100 #@param {type: 'integer'}\n",
        "# How many training examples we use in each step.\n",
        "TRAIN_BATCH_SIZE = 10 #@param {type: 'integer'}\n",
        "# How often to evaluate the model performance.\n",
        "EVAL_EVERY = 10 #@param {type: 'integer'}\n",
        "\n",
        "def get_batch(batch_size=None, test=False):\n",
        "  \"\"\"Get a random batch of examples.\"\"\"\n",
        "  examples = TEST_EXAMPLES if test else TRAIN_EXAMPLES\n",
        "  batch_examples = random.sample(examples, batch_size) if batch_size else examples\n",
        "  return batch_examples\n",
        "\n",
        "def get_images_and_labels(batch_examples):\n",
        "  images = [get_encoded_image(e) for e in batch_examples]\n",
        "  one_hot_labels = [get_label_one_hot(e) for e in batch_examples]\n",
        "  return images, one_hot_labels\n",
        "\n",
        "def get_label_one_hot(example):\n",
        "  \"\"\"Get the one hot encoding vector for the example.\"\"\"\n",
        "  one_hot_vector = np.zeros(NUM_CLASSES)\n",
        "  np.put(one_hot_vector, get_label(example), 1)\n",
        "  return one_hot_vector\n",
        "\n",
        "with tf.Session() as sess:\n",
        "  sess.run(tf.global_variables_initializer())\n",
        "  for i in range(NUM_TRAIN_STEPS):\n",
        "    # Get a random batch of training examples.\n",
        "    train_batch = get_batch(batch_size=TRAIN_BATCH_SIZE)\n",
        "    batch_images, batch_labels = get_images_and_labels(train_batch)\n",
        "    # Run the train_op to train the model.\n",
        "    train_loss, _, train_accuracy = sess.run(\n",
        "        [cross_entropy_mean, train_op, accuracy],\n",
        "        feed_dict={encoded_images: batch_images, labels: batch_labels})\n",
        "    is_final_step = (i == (NUM_TRAIN_STEPS - 1))\n",
        "    if i % EVAL_EVERY == 0 or is_final_step:\n",
        "      # Get a batch of test examples.\n",
        "      test_batch = get_batch(batch_size=None, test=True)\n",
        "      batch_images, batch_labels = get_images_and_labels(test_batch)\n",
        "      # Evaluate how well our model performs on the test set.\n",
        "      test_loss, test_accuracy, test_prediction, correct_predicate = sess.run(\n",
        "        [cross_entropy_mean, accuracy, prediction, correct_prediction],\n",
        "        feed_dict={encoded_images: batch_images, labels: batch_labels})\n",
        "      print('Test accuracy at step %s: %.2f%%' % (i, (test_accuracy * 100)))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ZFUNJxuH2t0V"
      },
      "outputs": [],
      "source": [
        "def show_confusion_matrix(test_labels, predictions):\n",
        "  \"\"\"Compute confusion matrix and normalize.\"\"\"\n",
        "  confusion = sk_metrics.confusion_matrix(\n",
        "    np.argmax(test_labels, axis=1), predictions)\n",
        "  confusion_normalized = confusion.astype(\"float\") / confusion.sum(axis=1)\n",
        "  axis_labels = list(CLASSES.values())\n",
        "  ax = sns.heatmap(\n",
        "      confusion_normalized, xticklabels=axis_labels, yticklabels=axis_labels,\n",
        "      cmap='Blues', annot=True, fmt='.2f', square=True)\n",
        "  plt.title(\"Confusion matrix\")\n",
        "  plt.ylabel(\"True label\")\n",
        "  plt.xlabel(\"Predicted label\")\n",
        "\n",
        "show_confusion_matrix(batch_labels, test_prediction)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Uu3vo8DK8BdL"
      },
      "source": [
        "## 不正確な予測\n",
        "\n",
        "モデルが間違えた test サンプルをもう少し詳しく見てみましょう。\n",
        "\n",
        "- test セットに誤ってラベル付けされたサンプルがありますか？\n",
        "- test セットに、実際には花の画像でないものなど、不適切なデータがありますか？\n",
        "- モデルがミスした原因がわかる画像はありますか？"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "hqa0V3WN8C9M"
      },
      "outputs": [],
      "source": [
        "incorrect = [\n",
        "    (example, CLASSES[prediction])\n",
        "    for example, prediction, is_correct in zip(test_batch, test_prediction, correct_predicate)\n",
        "    if not is_correct\n",
        "]\n",
        "display_images(\n",
        "  [(get_image(example), \"prediction: {0}\\nlabel:{1}\".format(incorrect_prediction, get_class(example)))\n",
        "   for (example, incorrect_prediction) in incorrect[:20]])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "YN_s04Il8TvK"
      },
      "source": [
        "## 演習: モデルを改善しましょう！\n",
        "\n",
        "ベースラインモデルはトレーニングしたので、正確性を高められるようにそれを改善してみましょう。（変更を適用するたびに、セルを実行し直す必要があります。）\n",
        "\n",
        "### 演習 1:  異なる画像モデルを試す\n",
        "\n",
        "TF-Hub では、異なる画像モデルを簡単に試すことができます。`hub.Module()` 内の `\"https://tfhub.dev/google/imagenet/mobilenet_v2_050_128/feature_vector/2\"` ハンドルを別のモジュールのハンドルに置き換えて、すべてのコードを実行し直すだけで可能です。利用可能なすべての画像モジュールは、[tfhub.dev](https://tfhub.dev/s?module-type=image-feature-vector) にあります。\n",
        "\n",
        "ほかの [MobileNet V2 モジュール](https://tfhub.dev/s?module-type=image-feature-vector&network-architecture=mobilenet-v2)を使用すると良いでしょう。MobileNet モジュールを含む多数のモジュールは、100 万個を超える画像と 1000 個のクラスを含む [ImageNet データセット](http://image-net.org/challenges/LSVRC/2012)を使ってトレーニングされたものです。ネットワークアーキテクチャの選択によって、速度と分類精度にトレードオフが生じます。MobileNet または NASNet Mobile は高速で小型ですが、Inception や ResNet などのより従来的なアーキテクチャは、精度を重視して設計されています。\n",
        "\n",
        "より大規模な Inception V3 アーキテクチャの場合は、目的のタスクにより近い領域で事前にトレーニングされているというメリットを得ることができます。植物や動物の[ iNaturalist データセットでトレーニングされたモジュール](https://tfhub.dev/google/inaturalist/inception_v3/feature_vector/1)として提供されています。\n",
        "\n",
        "### 演習 2: 非表示レイヤーを追加する\n",
        "\n",
        "抽出した画像特徴量と線形分類器の間に非表示レイヤーをスタックします（上記の `create_model()` 内）。100 個などのノードで非線形の非表示レイヤーを作成するには、[tf.layers.dense](https://www.tensorflow.org/api_docs/python/tf/compat/v1/layers/dense) を使用し、ユニットを 100、アクティベーションを `tf.nn.relu` に設定します。非表示レイヤーのサイズを変更すると、テストの精度に影響しますか？2 つ目の非表示レイヤーを追加すると、精度が改善されますか？\n",
        "\n",
        "### 演習 3: ハイパーパラメータを変更する\n",
        "\n",
        "*トレーニングステップ数*を変更すると、最終精度が改善されますか？*学習速度を変更*すると、モデルの収束を加速化できますか？トレーニングの*バッチサイズ*によって、モデルのパフォーマンスが変化しますか？\n",
        "\n",
        "### 演習 4: 異なるオプティマイザを試す\n",
        "\n",
        "基本の GradientDescentOptimizer を、[AdagradOptimizer](https://www.tensorflow.org/api_docs/python/tf/compat/v1/train/AdagradOptimizer) などのより高度な[オプティマイザ](https://www.tensorflow.org/api_guides/python/train#Optimizers)に置き換えてみましょう。モデルのトレーニングに変化はありますか？さまざまな最適化アルゴリズムのメリットの詳細については、[こちらの記事](http://ruder.io/optimizing-gradient-descent/)をご覧ください。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "kdwVXO1eJS5-"
      },
      "source": [
        "## 今後の学習\n",
        "\n",
        "このチュートリアルのさらに高度なバージョンに興味がある方は、TensorBoard を使用したトレーニングの視覚化、画像に歪みを与えてデータセットを拡張する高度なテクニック、および flowers データセットを置き換えて独自のデータセットで画像分類器を学習する方法を説明した [TensorFlow 画像の再トレーニングチュートリアル](https://www.tensorflow.org/hub/tutorials/image_retraining)をご覧ください。\n",
        "\n",
        "[tensorflow.org/hub](http://tensorflow.org) では、TensorFlow についてさらに学習し、TF-Hub API ドキュメントを確認することができます。また、[tfhub.dev](https://www.tensorflow.org/hub/) では、その他の画像特徴量ベクトルモジュールやテキスト埋め込みモジュールなど、利用可能な TensorFlow Hub モジュールを検索することができます。\n",
        "\n",
        "さらに、Google の [Machine Learning Crash Course](https://developers.google.com/machine-learning/crash-course/) もご覧ください。機械学習の実用的な導入をテンポよく学習できます。"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [
        "ScitaPqhKtuW"
      ],
      "name": "image_feature_vector.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
